/**
 * The Whirlpool hashing function.
 *
 * <P>
 * <b>References</b>
 *
 * <P>
 * The Whirlpool algorithm was developed by
 * <a href="mailto:pbarreto@scopus.com.br">Paulo S. L. M. Barreto</a> and
 * <a href="mailto:vincent.rijmen@cryptomathic.com">Vincent Rijmen</a>.
 *
 * See
 *      P.S.L.M. Barreto, V. Rijmen,
 *      ``The Whirlpool hashing function,''
 *      First NESSIE workshop, 2000 (tweaked version, 2003),
 *      <https://www.cosic.esat.kuleuven.ac.be/nessie/workshop/submissions/whirlpool.zip>
 * 
 * @author    Paulo S.L.M. Barreto
 * @author    Vincent Rijmen.
 *
 * @version 3.0 (2003.03.12)
 *
 * =============================================================================
 *
 * Differences from version 2.1:
 *
 * - Suboptimal diffusion matrix replaced by cir(1, 1, 4, 1, 8, 5, 2, 9).
 *
 * =============================================================================
 *
 * Differences from version 2.0:
 *
 * - Generation of ISO/IEC 10118-3 test vectors.
 * - Bug fix: nonzero carry was ignored when tallying the data length
 *      (this bug apparently only manifested itself when feeding data
 *      in pieces rather than in a single chunk at once).
 *
 * Differences from version 1.0:
 *
 * - Original S-box replaced by the tweaked, hardware-efficient version.
 *
 * =============================================================================
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
package de.fhkl.dbsm.zvafzwects.utils.hash;

import java.util.Arrays;

public class Whirlpool {

	/**
	 * The message digest size (in bits)
	 */
	public static final int DIGESTBITS = 512;

	/**
	 * The message digest size (in bytes)
	 */
	public static final int DIGESTBYTES = DIGESTBITS >>> 3;

	/**
	 * The number of rounds of the internal dedicated block cipher.
	 */
	protected static final int R = 10;

	/**
	 * The substitution box.
	 */
	private static final String sbox = "\u1823\uc6E8\u87B8\u014F\u36A6\ud2F5\u796F\u9152"
			+ "\u60Bc\u9B8E\uA30c\u7B35\u1dE0\ud7c2\u2E4B\uFE57"
			+ "\u1577\u37E5\u9FF0\u4AdA\u58c9\u290A\uB1A0\u6B85"
			+ "\uBd5d\u10F4\ucB3E\u0567\uE427\u418B\uA77d\u95d8"
			+ "\uFBEE\u7c66\udd17\u479E\ucA2d\uBF07\uAd5A\u8333"
			+ "\u6302\uAA71\uc819\u49d9\uF2E3\u5B88\u9A26\u32B0"
			+ "\uE90F\ud580\uBEcd\u3448\uFF7A\u905F\u2068\u1AAE"
			+ "\uB454\u9322\u64F1\u7312\u4008\uc3Ec\udBA1\u8d3d"
			+ "\u9700\ucF2B\u7682\ud61B\uB5AF\u6A50\u45F3\u30EF"
			+ "\u3F55\uA2EA\u65BA\u2Fc0\udE1c\uFd4d\u9275\u068A"
			+ "\uB2E6\u0E1F\u62d4\uA896\uF9c5\u2559\u8472\u394c"
			+ "\u5E78\u388c\ud1A5\uE261\uB321\u9c1E\u43c7\uFc04"
			+ "\u5199\u6d0d\uFAdF\u7E24\u3BAB\ucE11\u8F4E\uB7EB"
			+ "\u3c81\u94F7\uB913\u2cd3\uE76E\uc403\u5644\u7FA9"
			+ "\u2ABB\uc153\udc0B\u9d6c\u3174\uF646\uAc89\u14E1"
			+ "\u163A\u6909\u70B6\ud0Ed\ucc42\u98A4\u285c\uF886";

	private static long[][] C = new long[8][256];
	private static long[] rc = new long[R + 1];

	static {
		for (int x = 0; x < 256; x++) {
			char c = sbox.charAt(x / 2);
			long v1 = ((x & 1) == 0) ? c >>> 8 : c & 0xff;
			long v2 = v1 << 1;
			if (v2 >= 0x100L) {
				v2 ^= 0x11dL;
			}
			long v4 = v2 << 1;
			if (v4 >= 0x100L) {
				v4 ^= 0x11dL;
			}
			long v5 = v4 ^ v1;
			long v8 = v4 << 1;
			if (v8 >= 0x100L) {
				v8 ^= 0x11dL;
			}
			long v9 = v8 ^ v1;
			/*
			 * build the circulant table C[0][x] = S[x].[1, 1, 4, 1, 8, 5, 2,
			 * 9]:
			 */
			C[0][x] = (v1 << 56) | (v1 << 48) | (v4 << 40) | (v1 << 32)
					| (v8 << 24) | (v5 << 16) | (v2 << 8) | (v9);
			/*
			 * build the remaining circulant tables C[t][x] = C[0][x] rotr t
			 */
			for (int t = 1; t < 8; t++) {
				C[t][x] = (C[t - 1][x] >>> 8) | ((C[t - 1][x] << 56));
			}
		}
		/*
		 * for (int t = 0; t < 8; t++) { System.out.println("static const u64 C"
		 * + t + "[256] = {"); for (int i = 0; i < 64; i++) {
		 * System.out.print("   "); for (int j = 0; j < 4; j++) { String v =
		 * Long.toHexString(C[t][4*i + j]); while (v.length() < 16) { v = "0" +
		 * v; } System.out.print(" LL(0x" + v + "),"); } System.out.println(); }
		 * System.out.println("};"); System.out.println(); }
		 * System.out.println(); //
		 */

		/*
		 * build the round constants:
		 */
		rc[0] = 0L; /*
					 * not used (assigment kept only to properly initialize all
					 * variables)
					 */
		for (int r = 1; r <= R; r++) {
			int i = 8 * (r - 1);
			rc[r] = (C[0][i] & 0xff00000000000000L)
					^ (C[1][i + 1] & 0x00ff000000000000L)
					^ (C[2][i + 2] & 0x0000ff0000000000L)
					^ (C[3][i + 3] & 0x000000ff00000000L)
					^ (C[4][i + 4] & 0x00000000ff000000L)
					^ (C[5][i + 5] & 0x0000000000ff0000L)
					^ (C[6][i + 6] & 0x000000000000ff00L)
					^ (C[7][i + 7] & 0x00000000000000ffL);
		}
		/*
		 * System.out.println("static const u64 rc[R + 1] = {"); for (int r = 0;
		 * r <= R; r++) { String v = Long.toHexString(rc[r]); while (v.length()
		 * < 16) { v = "0" + v; } System.out.println("    LL(0x" + v + "),"); }
		 * System.out.println("};"); System.out.println(); //
		 */
	}

	/**
	 * Global number of hashed bits (256-bit counter).
	 */
	protected byte[] bitLength = new byte[32];

	/**
	 * Buffer of data to hash.
	 */
	protected byte[] buffer = new byte[64];

	/**
	 * Current number of bits on the buffer.
	 */
	protected int bufferBits = 0;

	/**
	 * Current (possibly incomplete) byte slot on the buffer.
	 */
	protected int bufferPos = 0;

	/**
	 * The hashing state.
	 */
	protected long[] hash = new long[8];
	protected long[] K = new long[8]; // the round key
	protected long[] L = new long[8];
	protected long[] block = new long[8]; // mu(buffer)
	protected long[] state = new long[8]; // the cipher state

	public Whirlpool() {
	}

	/**
	 * The core Whirlpool transform.
	 */
	protected void processBuffer() {
		/*
		 * map the buffer to a block:
		 */
		for (int i = 0, j = 0; i < 8; i++, j += 8) {
			block[i] = (((long) buffer[j]) << 56)
					^ (((long) buffer[j + 1] & 0xffL) << 48)
					^ (((long) buffer[j + 2] & 0xffL) << 40)
					^ (((long) buffer[j + 3] & 0xffL) << 32)
					^ (((long) buffer[j + 4] & 0xffL) << 24)
					^ (((long) buffer[j + 5] & 0xffL) << 16)
					^ (((long) buffer[j + 6] & 0xffL) << 8)
					^ (((long) buffer[j + 7] & 0xffL));
		}
		/*
		 * compute and apply K^0 to the cipher state:
		 */
		for (int i = 0; i < 8; i++) {
			state[i] = block[i] ^ (K[i] = hash[i]);
		}
		/*
		 * iterate over all rounds:
		 */
		for (int r = 1; r <= R; r++) {
			/*
			 * compute K^r from K^{r-1}:
			 */
			for (int i = 0; i < 8; i++) {
				L[i] = 0L;
				for (int t = 0, s = 56; t < 8; t++, s -= 8) {
					L[i] ^= C[t][(int) (K[(i - t) & 7] >>> s) & 0xff];
				}
			}
			for (int i = 0; i < 8; i++) {
				K[i] = L[i];
			}
			K[0] ^= rc[r];
			/*
			 * apply the r-th round transformation:
			 */
			for (int i = 0; i < 8; i++) {
				L[i] = K[i];
				for (int t = 0, s = 56; t < 8; t++, s -= 8) {
					L[i] ^= C[t][(int) (state[(i - t) & 7] >>> s) & 0xff];
				}
			}
			for (int i = 0; i < 8; i++) {
				state[i] = L[i];
			}
		}
		/*
		 * apply the Miyaguchi-Preneel compression function:
		 */
		for (int i = 0; i < 8; i++) {
			hash[i] ^= state[i] ^ block[i];
		}
	}

	/**
	 * Initialize the hashing state.
	 */
	public void NESSIEinit() {
		Arrays.fill(bitLength, (byte) 0);
		bufferBits = bufferPos = 0;
		buffer[0] = 0; // it's only necessary to cleanup buffer[bufferPos].
		Arrays.fill(hash, 0L); // initial value
	}

	/**
	 * Delivers input data to the hashing algorithm.
	 * 
	 * @param source
	 * @param sourceBits
	 *            how many bits of plaintext to process.
	 * 
	 *            This method maintains the invariant: bufferBits < 512
	 */
	public void NESSIEadd(byte[] source, long sourceBits) {
		/*
		 * sourcePos | +-------+-------+------- ||||||||||||||||||||| source
		 * +-------+-------+-------
		 * +-------+-------+-------+-------+-------+-------
		 * |||||||||||||||||||||| buffer
		 * +-------+-------+-------+-------+-------+------- | bufferPos
		 */
		int sourcePos = 0; // index of leftmost source byte containing data (1
							// to 8 bits).
		int sourceGap = (8 - ((int) sourceBits & 7)) & 7; // space on
															// source[sourcePos].
		int bufferRem = bufferBits & 7; // occupied bits on buffer[bufferPos].
		int b;
		// tally the length of the added data:
		long value = sourceBits;
		for (int i = 31, carry = 0; i >= 0; i--) {
			carry += (bitLength[i] & 0xff) + ((int) value & 0xff);
			bitLength[i] = (byte) carry;
			carry >>>= 8;
			value >>>= 8;
		}
		// process data in chunks of 8 bits:
		while (sourceBits > 8) { // at least source[sourcePos] and
									// source[sourcePos+1] contain data.
			// take a byte from the source:
			b = ((source[sourcePos] << sourceGap) & 0xff)
					| ((source[sourcePos + 1] & 0xff) >>> (8 - sourceGap));
			if (b < 0 || b >= 256) {
				throw new RuntimeException("LOGIC ERROR");
			}
			// process this byte:
			buffer[bufferPos++] |= b >>> bufferRem;
			bufferBits += 8 - bufferRem; // bufferBits = 8*bufferPos;
			if (bufferBits == 512) {
				// process data block:
				processBuffer();
				// reset buffer:
				bufferBits = bufferPos = 0;
			}
			buffer[bufferPos] = (byte) ((b << (8 - bufferRem)) & 0xff);
			bufferBits += bufferRem;
			// proceed to remaining data:
			sourceBits -= 8;
			sourcePos++;
		}
		// now 0 <= sourceBits <= 8;
		// furthermore, all data (if any is left) is in source[sourcePos].
		if (sourceBits > 0) {
			b = (source[sourcePos] << sourceGap) & 0xff; // bits are
															// left-justified on
															// b.
			// process the remaining bits:
			buffer[bufferPos] |= b >>> bufferRem;
		} else {
			b = 0;
		}
		if (bufferRem + sourceBits < 8) {
			// all remaining data fits on buffer[bufferPos], and there still
			// remains some space.
			bufferBits += sourceBits;
		} else {
			// buffer[bufferPos] is full:
			bufferPos++;
			bufferBits += 8 - bufferRem; // bufferBits = 8*bufferPos;
			sourceBits -= 8 - bufferRem;
			// now 0 <= sourceBits < 8; furthermore, all data is in
			// source[sourcePos].
			if (bufferBits == 512) {
				// process data block:
				processBuffer();
				// reset buffer:
				bufferBits = bufferPos = 0;
			}
			buffer[bufferPos] = (byte) ((b << (8 - bufferRem)) & 0xff);
			bufferBits += (int) sourceBits;
		}
	}

	/**
	 * Get the hash value from the hashing state.
	 * 
	 * This method uses the invariant: bufferBits < 512
	 */
	public void NESSIEfinalize(byte[] digest) {
		// append a '1'-bit:
		buffer[bufferPos] |= 0x80 >>> (bufferBits & 7);
		bufferPos++; // all remaining bits on the current byte are set to zero.
		// pad with zero bits to complete 512N + 256 bits:
		if (bufferPos > 32) {
			while (bufferPos < 64) {
				buffer[bufferPos++] = 0;
			}
			// process data block:
			processBuffer();
			// reset buffer:
			bufferPos = 0;
		}
		while (bufferPos < 32) {
			buffer[bufferPos++] = 0;
		}
		// append bit length of hashed data:
		System.arraycopy(bitLength, 0, buffer, 32, 32);
		// process data block:
		processBuffer();
		// return the completed message digest:
		for (int i = 0, j = 0; i < 8; i++, j += 8) {
			long h = hash[i];
			digest[j] = (byte) (h >>> 56);
			digest[j + 1] = (byte) (h >>> 48);
			digest[j + 2] = (byte) (h >>> 40);
			digest[j + 3] = (byte) (h >>> 32);
			digest[j + 4] = (byte) (h >>> 24);
			digest[j + 5] = (byte) (h >>> 16);
			digest[j + 6] = (byte) (h >>> 8);
			digest[j + 7] = (byte) (h);
		}
	}

	/**
	 * Delivers string input data to the hashing algorithm.
	 * 
	 * @param source
	 *            plaintext data to hash (ASCII text string).
	 * 
	 *            This method maintains the invariant: bufferBits < 512
	 */
	public void NESSIEadd(String source) {
		if (source.length() > 0) {
			byte[] data = new byte[source.length()];
			for (int i = 0; i < source.length(); i++) {
				data[i] = (byte) source.charAt(i);
			}
			NESSIEadd(data, 8 * data.length);
		}
	}

	private static String display(byte[] array) {
		char[] val = new char[2 * array.length];
		String hex = "0123456789ABCDEF";
		for (int i = 0; i < array.length; i++) {
			int b = array[i] & 0xff;
			val[2 * i] = hex.charAt(b >>> 4);
			val[2 * i + 1] = hex.charAt(b & 15);
		}
		return String.valueOf(val);
	}

	private static final int LONG_ITERATION = 100000000;

	/**
	 * Generate the NESSIE test vector set for Whirlpool.
	 * 
	 * The test consists of: 1. hashing all bit strings containing only zero
	 * bits for all lengths from 0 to 1023; 2. hashing all 512-bit strings
	 * containing a single set bit; 3. the iterated hashing of the 512-bit
	 * string of zero bits a large number of times.
	 */
	public static void makeNESSIETestVectors() {
		Whirlpool w = new Whirlpool();
		byte[] digest = new byte[64];
		byte[] data = new byte[128];
		Arrays.fill(data, (byte) 0);
		System.out
				.println("Message digests of strings of 0-bits and length L:");
		for (int i = 0; i < 1024; i++) {
			w.NESSIEinit();
			w.NESSIEadd(data, i);
			w.NESSIEfinalize(digest);
			String s = Integer.toString(i);
			s = "     ".substring(s.length()) + s;
			System.out.println("    L =" + s + ": " + display(digest));
		}
		System.out
				.println("Message digests of all 512-bit strings S containing a single 1-bit:");
		data = new byte[512 / 8];
		Arrays.fill(data, (byte) 0);
		for (int i = 0; i < 512; i++) {
			// set bit i:
			data[i / 8] |= 0x80 >>> (i % 8);
			w.NESSIEinit();
			w.NESSIEadd(data, 512);
			w.NESSIEfinalize(digest);
			System.out.println("    S = " + display(data) + ": "
					+ display(digest));
			// reset bit i:
			data[i / 8] = 0;
		}
		for (int i = 0; i < digest.length; i++) {
			digest[i] = 0;
		}
		for (int i = 0; i < LONG_ITERATION; i++) {
			w.NESSIEinit();
			w.NESSIEadd(digest, 512);
			w.NESSIEfinalize(digest);
		}
		System.out.println("Iterated message digest computation ("
				+ LONG_ITERATION + " times): " + display(digest));
	}

	/**
	 * Generate the ISO/IEC 10118-3 test vector set for Whirlpool.
	 */
	public static void makeISOTestVectors() {
		Whirlpool w = new Whirlpool();
		byte[] digest = new byte[DIGESTBYTES];
		byte[] data = new byte[1000000];

		Arrays.fill(data, (byte) 0);

		System.out
				.println("1. In this example the data-string is the empty string, i.e. the string of length zero.\n");
		w.NESSIEinit();
		w.NESSIEfinalize(digest);
		System.out.println("The hash-code is the following 512-bit string.\n\n"
				+ display(digest) + "\n");

		System.out
				.println("2. In this example the data-string consists of a single byte, namely the ASCII-coded version of the letter 'a'.\n");
		w.NESSIEinit();
		w.NESSIEadd("a");
		w.NESSIEfinalize(digest);
		System.out.println("The hash-code is the following 512-bit string.\n\n"
				+ display(digest) + "\n");

		System.out
				.println("3. In this example the data-string is the three-byte string consisting of the ASCII-coded version of 'abc'.\n");
		w.NESSIEinit();
		w.NESSIEadd("abc");
		w.NESSIEfinalize(digest);
		System.out.println("The hash-code is the following 512-bit string.\n\n"
				+ display(digest) + "\n");

		System.out
				.println("4. In this example the data-string is the 14-byte string consisting of the ASCII-coded version of 'message digest'.\n");
		w.NESSIEinit();
		w.NESSIEadd("message digest");
		w.NESSIEfinalize(digest);
		System.out.println("The hash-code is the following 512-bit string.\n\n"
				+ display(digest) + "\n");

		System.out
				.println("5. In this example the data-string is the 26-byte string consisting of the ASCII-coded version of 'abcdefghijklmnopqrstuvwxyz'.\n");
		w.NESSIEinit();
		w.NESSIEadd("abcdefghijklmnopqrstuvwxyz");
		w.NESSIEfinalize(digest);
		System.out.println("The hash-code is the following 512-bit string.\n\n"
				+ display(digest) + "\n");

		System.out
				.println("6. In this example the data-string is the 62-byte string consisting of the ASCII-coded version of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.\n");
		w.NESSIEinit();
		w.NESSIEadd("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
		w.NESSIEfinalize(digest);
		System.out.println("The hash-code is the following 512-bit string.\n\n"
				+ display(digest) + "\n");

		System.out
				.println("7. In this example the data-string is the 80-byte string consisting of the ASCII-coded version of eight repetitions of '1234567890'.\n");
		w.NESSIEinit();
		w.NESSIEadd("12345678901234567890123456789012345678901234567890123456789012345678901234567890");
		w.NESSIEfinalize(digest);
		System.out.println("The hash-code is the following 512-bit string.\n\n"
				+ display(digest) + "\n");

		System.out
				.println("8. In this example the data-string is the 32-byte string consisting of the ASCII-coded version of 'abcdbcdecdefdefgefghfghighijhijk'.\n");
		w.NESSIEinit();
		w.NESSIEadd("abcdbcdecdefdefgefghfghighijhijk");
		w.NESSIEfinalize(digest);
		System.out.println("The hash-code is the following 512-bit string.\n\n"
				+ display(digest) + "\n");

		Arrays.fill(data, (byte) 'a');
		System.out
				.println("9. In this example the data-string is the 1000000-byte string consisting of the ASCII-coded version of 'a' repeated 10^6 times.\n");
		w.NESSIEinit();
		w.NESSIEadd(data, 8 * 1000000);
		w.NESSIEfinalize(digest);
		System.out.println("The hash-code is the following 512-bit string.\n\n"
				+ display(digest) + "\n");
	}
	
	/**
	 * @author Markus Henn
	 * @param source plaintext data to hash (ASCII text string).
	 * @return the whirlpool hash in hexadecimal.
	 */
	public static String hash(String source) {
		Whirlpool w = new Whirlpool();
		byte[] digest = new byte[64];
		w.NESSIEinit();
		w.NESSIEadd(source);
		w.NESSIEfinalize(digest);
		return display(digest);
	}

	public static void main(String[] args) {
		// makeNESSIETestVectors();
		makeISOTestVectors();
		System.out.println("test: " + hash("test"));
	}
}
